package com.unknow.first.dapp.admin.account.service.impl;


import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.unknow.first.dapp.admin.account.constants.AccountConstants.SystemAddressType;
import com.unknow.first.dapp.admin.account.constants.AccountConstants.TradeRemarkColumn;
import com.unknow.first.dapp.admin.account.constants.AccountConstants.TradeStatus;
import com.unknow.first.dapp.admin.account.constants.AccountConstants.TradeType;
import com.unknow.first.dapp.admin.account.constants.FlashSwapConstants.PairStatus;
import com.unknow.first.dapp.admin.account.dto.AddressSwapDTO;
import com.unknow.first.dapp.admin.account.dto.AddressSwapEstimateDTO;
import com.unknow.first.dapp.admin.account.dto.AddressSwapEstimateDTO.AddressSwapEstimateDTOBuilder;
import com.unknow.first.dapp.admin.account.mapper.AddressCoinConfig;
import com.unknow.first.dapp.admin.account.mapper.FlashSwapPair;
import com.unknow.first.dapp.admin.account.mapper.FlashSwapRecord;
import com.unknow.first.dapp.admin.account.mapper.FlashSwapRecord.FlashSwapRecordBuilder;
import com.unknow.first.dapp.admin.account.mapper.FlashSwapRecordMapper;
import com.unknow.first.dapp.admin.account.service.*;
import com.unknow.first.dapp.admin.account.utils.FlashSwapUtil;
import com.unknow.first.dapp.admin.account.utils.SystemAddressUtil;
import org.cloud.context.RequestContextManager;
import org.cloud.entity.LoginUserDetails;
import org.cloud.exception.BusinessException;
import org.cloud.utils.SpringContextUtil;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import java.math.BigDecimal;
import java.math.MathContext;
import java.util.List;
import java.util.Objects;

import static com.unknow.first.dapp.admin.account.constants.AccountErrorConstants.*;
import static com.unknow.first.dapp.admin.account.service.ISwapRecordAfterService.SWAP_RECORD_AFTER_SERVICE_PREFIX;
import static com.unknow.first.dapp.admin.account.service.ISwapRecordBeforeService.SWAP_RECORD_BEFORE_SERVICE_PREFIX;

@Service
public class FlashSwapRecordServiceImpl extends ServiceImpl<FlashSwapRecordMapper, FlashSwapRecord> implements FlashSwapRecordService {

    @Override
    public int insertSelective(FlashSwapRecord record) {
        return baseMapper.insertSelective(record);
    }

    @Override
    public int updateByPrimaryKeySelective(FlashSwapRecord record) {
        return baseMapper.updateByPrimaryKeySelective(record);
    }

    @Override
    public int updateBatch(List<FlashSwapRecord> list) {
        return baseMapper.updateBatch(list);
    }

    @Override
    public int batchInsert(List<FlashSwapRecord> list) {
        return baseMapper.batchInsert(list);
    }

    @Autowired
    FlashSwapPairService flashSwapPairService;

    @Autowired
    AddressCoinConfigService coinConfigService;

    @Autowired
    AddressAccountInfoService addressAccountInfoService;

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public FlashSwapRecord create(AddressSwapDTO createDTO, String address) throws Exception {
        Assert.isTrue(createDTO.getAmount().compareTo(BigDecimal.ZERO) > 0, "system.error.amount.must.larger.zero");
        FlashSwapPair flashSwapPair = flashSwapPairService.getBaseMapper().selectOne(
            Wrappers.<FlashSwapPair>lambdaQuery().eq(FlashSwapPair::getSwapPairId, createDTO.getSwapPairId())
                .eq(FlashSwapPair::getStatus, PairStatus.ACTIVE.value));
        if (flashSwapPair == null) {
            throw new BusinessException(PAIR_ID_IS_NOT_EXISTS.code);
        }

        if (!Objects.equals(createDTO.getFromCoinId(), flashSwapPair.getCoin1Id()) && !Objects.equals(createDTO.getFromCoinId(), flashSwapPair.getCoin2Id())) {
            throw new BusinessException(PAIR_ID_IS_NOT_CORRECT.code);
        }

        //校验兑换方向
        checkSwapDirection(createDTO, flashSwapPair);

        CalcResult result = getCalcResult(createDTO, flashSwapPair);
        Assert.isTrue(result.isInSwapLimit, SWAP_AMOUNT_IS_NOTIN_LIMIT.code);

        Integer fromCoinId = createDTO.getFromCoinId();
        AddressCoinConfig fromCoinInfo = coinConfigService.getOne(Wrappers.<AddressCoinConfig>lambdaQuery().eq(AddressCoinConfig::getCoinId, fromCoinId));
        AddressCoinConfig toCoinInfo = coinConfigService.getOne(Wrappers.<AddressCoinConfig>lambdaQuery().eq(AddressCoinConfig::getCoinId, result.toCoinId));
        FlashSwapRecordBuilder<?, ?> swapRecordCreateBuilder = FlashSwapRecord.builder();
        swapRecordCreateBuilder.swapPairId(createDTO.getSwapPairId()).address(address);
        swapRecordCreateBuilder.charge(result.chargeAmount).price(flashSwapPair.getPrice())
            .priceTag(flashSwapPair.getCoin2Symbol() + "/" + flashSwapPair.getCoin1Symbol());
        swapRecordCreateBuilder.fromCoinId(fromCoinId).fromCoinName(fromCoinInfo.getCoinName()).fromCoinSymbol(fromCoinInfo.getCoinSymbol());
        swapRecordCreateBuilder.toCoinId(result.toCoinId).toCoinName(toCoinInfo.getCoinName()).toCoinSymbol(toCoinInfo.getCoinSymbol());
        swapRecordCreateBuilder.fromCoinAmount(createDTO.getAmount()).toCoinAmount(result.toAmount);
        FlashSwapRecord swapRecordCreate = swapRecordCreateBuilder.build();
        baseMapper.insert(swapRecordCreate);  // 操作记录，相关的限制校验在保存的触发器中处理

        final String pairName = FlashSwapUtil.single().getPairNameById(createDTO.getSwapPairId());

        ISwapRecordBeforeService beforeService = SpringContextUtil.getBean(SWAP_RECORD_BEFORE_SERVICE_PREFIX + pairName);
        if (beforeService != null) {
            beforeService.execute(swapRecordCreate);
        }

        final LoginUserDetails currentUser = RequestContextManager.single().getRequestContext().getUser();
        JSONObject remarkObject = new JSONObject();
        // 记录手续费
        if (result.chargeAmount.compareTo(BigDecimal.ZERO) > 0) {
            String chargeAccount = SystemAddressUtil.single().getFirstSystemAddress(SystemAddressType.SERVICE_CHARGE);
            remarkObject.put(TradeRemarkColumn.PAY_TO.code, chargeAccount);
            addressAccountInfoService.processFundAndDetail(BigDecimal.ZERO.subtract(result.chargeAmount), fromCoinInfo, TradeType.FLASH_SWAP_CHARGE,
                remarkObject.toJSONString(), swapRecordCreate.getFlashSwapRecordId(), address, currentUser.getUsername(), TradeStatus.DONE.value);

            remarkObject.clear();
            remarkObject.put(TradeRemarkColumn.PAY_BY.code, address);
            addressAccountInfoService.processFundAndDetail(result.chargeAmount, fromCoinInfo, TradeType.FLASH_SWAP_CHARGE, remarkObject.toJSONString(),
                swapRecordCreate.getFlashSwapRecordId(), chargeAccount, currentUser.getUsername(), TradeStatus.DONE.value);
        }

        remarkObject.clear();
        remarkObject.put(TradeRemarkColumn.PAY_TO.code, "FLASH_SWAP");
        addressAccountInfoService.processFundAndDetail(BigDecimal.ZERO.subtract(result.fromAmount), fromCoinInfo, TradeType.FLASH_SWAP,
            remarkObject.toJSONString(), swapRecordCreate.getFlashSwapRecordId(), address, currentUser.getUsername(), TradeStatus.DONE.value);

        remarkObject.clear();
        remarkObject.put(TradeRemarkColumn.PAY_BY.code, "FLASH_SWAP");
        addressAccountInfoService.processFundAndDetail(result.toAmount, toCoinInfo, TradeType.FLASH_SWAP, remarkObject.toJSONString(),
            swapRecordCreate.getFlashSwapRecordId(), address, currentUser.getUsername(), TradeStatus.DONE.value);

        ISwapRecordAfterService afterService = SpringContextUtil.getBean(SWAP_RECORD_AFTER_SERVICE_PREFIX + pairName);
        if (afterService != null) {
            afterService.execute(swapRecordCreate);
        }

        return swapRecordCreate;
    }

    /**
     * 校验兑换方向开通情况
     * @param createDTO
     * @param flashSwapPair
     */
    private void checkSwapDirection(AddressSwapDTO createDTO, FlashSwapPair flashSwapPair){
        if (createDTO.getFromCoinId().equals(flashSwapPair.getCoin2Id())) { //买coin1
            Assert.isTrue(flashSwapPair.getCoin1LimitUserPerDay().compareTo(BigDecimal.ZERO) > 0
                    && flashSwapPair.getCoin1LimitPerDay().compareTo(BigDecimal.ZERO) > 0, SWAP_DIRECTION_CLOSE.code);
        }else { //买coin2
            Assert.isTrue(flashSwapPair.getCoin2LimitUserPerDay().compareTo(BigDecimal.ZERO) > 0
                    && flashSwapPair.getCoin2LimitPerDay().compareTo(BigDecimal.ZERO) > 0, SWAP_DIRECTION_CLOSE.code);
        }
    }

    @NotNull
    private static CalcResult getCalcResult(AddressSwapDTO createDTO, FlashSwapPair flashSwapPair) {
        final BigDecimal chargeAmount = createDTO.getAmount().multiply(flashSwapPair.getChargePercent(), MathContext.DECIMAL32).setScale(18);
        final BigDecimal fromAmount = createDTO.getAmount().subtract(chargeAmount).setScale(18);

        Integer toCoinId = flashSwapPair.getCoin2Id();
        BigDecimal toAmount;
        Boolean isInSwapLimit;
        if (createDTO.getFromCoinId().equals(flashSwapPair.getCoin2Id())) {
            toCoinId = flashSwapPair.getCoin1Id();
            toAmount = fromAmount.divide(flashSwapPair.getPrice(), MathContext.DECIMAL32).setScale(18);
            isInSwapLimit = toAmount.compareTo(flashSwapPair.getCoin1MaxLimit()) < 1 && toAmount.compareTo(flashSwapPair.getCoin1MinLimit()) > -1;
        } else {
            toAmount = fromAmount.multiply(flashSwapPair.getPrice(), MathContext.DECIMAL32).setScale(18);
            isInSwapLimit = fromAmount.compareTo(flashSwapPair.getCoin1MaxLimit()) < 1 && fromAmount.compareTo(flashSwapPair.getCoin1MinLimit()) > -1;
        }
        CalcResult result = new CalcResult(chargeAmount, fromAmount, toCoinId, toAmount, isInSwapLimit);
        return result;
    }

    private static class CalcResult {

        public final BigDecimal chargeAmount;
        public final BigDecimal fromAmount;
        public final Integer toCoinId;
        public final BigDecimal toAmount;
        public final Boolean isInSwapLimit;

        public CalcResult(BigDecimal chargeAmount, BigDecimal fromAmount, Integer toCoinId, BigDecimal toAmount, Boolean isInSwapLimit) {
            this.chargeAmount = chargeAmount;
            this.fromAmount = fromAmount;
            this.toCoinId = toCoinId;
            this.toAmount = toAmount;
            this.isInSwapLimit = isInSwapLimit;
        }
    }

    @Override
    public AddressSwapEstimateDTO estimate(AddressSwapDTO createDTO, String address) throws Exception {
        FlashSwapPair flashSwapPair = flashSwapPairService.getById(createDTO.getSwapPairId());
        CalcResult result = getCalcResult(createDTO, flashSwapPair);
        AddressCoinConfig fromCoinInfo = coinConfigService.getOne(
            Wrappers.<AddressCoinConfig>lambdaQuery().eq(AddressCoinConfig::getCoinId, createDTO.getFromCoinId()));
        AddressCoinConfig toCoinInfo = coinConfigService.getOne(Wrappers.<AddressCoinConfig>lambdaQuery().eq(AddressCoinConfig::getCoinId, result.toCoinId));
        AddressSwapEstimateDTOBuilder<?, ?> builder = AddressSwapEstimateDTO.builder();
        builder.charge(result.chargeAmount).fromCoinAmount(result.fromAmount).toCoinAmount(result.toAmount);
        builder.fromCoinName(fromCoinInfo.getCoinName()).fromCoinSymbol(fromCoinInfo.getCoinSymbol());
        builder.toCoinName(toCoinInfo.getCoinName()).toCoinSymbol(toCoinInfo.getCoinSymbol());

        BigDecimal totalSwapAmount = baseMapper.queryTotalSwap(result.toCoinId, null);
        BigDecimal userTotalSwapAmount = address == null ? BigDecimal.ZERO : baseMapper.queryTotalSwap(result.toCoinId, address);

        totalSwapAmount = totalSwapAmount == null ? BigDecimal.ZERO : totalSwapAmount;
        userTotalSwapAmount = userTotalSwapAmount == null ? BigDecimal.ZERO : userTotalSwapAmount;

        if (result.toCoinId == flashSwapPair.getCoin1Id()) {
            builder.residueCoinAmount(flashSwapPair.getCoin1LimitPerDay().subtract(totalSwapAmount));
            builder.userResidueCoinAmount(flashSwapPair.getCoin1LimitUserPerDay().subtract(userTotalSwapAmount));
        } else if (result.toCoinId == flashSwapPair.getCoin2Id()) {
            builder.residueCoinAmount(flashSwapPair.getCoin2LimitPerDay().subtract(totalSwapAmount));
            builder.userResidueCoinAmount(flashSwapPair.getCoin2LimitUserPerDay().subtract(userTotalSwapAmount));
        }

        return builder.build();
    }

}
